今天的更新內容主要是建立一個簡單的網頁伺服器,並透過表單與API來接受使用者的輸入。
我們將一步一步地講解每個部分的設計理念,以及可能存在的安全問題,特別是如何防範 HTML注入、CSS注入、和 JavaScript注入 等漏洞。
docker-compose.yml
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- ./web:/usr/src/app
- /usr/src/app/node_modules
command: sh -c "npm install && npm start"
mongo:
image: mongo:latest
command: sh -c "npm install && npm start"
的原因:自動化根據相依性的套件進行安裝,避免因為缺少套件而導致應用程式無法正常執行。sh -c
:使用 Shell 執行多個指令。&&
:確保前一個指令(npm install)成功執行後,才會執行下一個命令(npm start)。如果安裝失敗,應用程式不會啟動,幫助開發者及早發現問題。npm install
會根據 package.json
安裝所有的相依套件,有一些相依套件根據 postinstall
這些腳本會自動執行,如果相依套件的內容被「惡意竄改」可能包含有害腳本,若在 Docker 容器中執行,可能會導致資安漏洞。root
進行執行,雖然容器可能有進行隔離,但是仍可能有「容器逃逸漏洞」。
web/public/index.html
<html>
<head>
<meta charset="UTF-8">
<title>歡迎來到測試網頁</title>
</head>
<body>
<h1>歡迎來到測試網頁</h1>
<button onclick="fetchMessage()">從 API 取得資訊</button>
<div id="message"></div>
進入 <a href="/form">表單頁面</a>
<script>
function fetchMessage() {
fetch('/api/hello')
.then(response => response.json())
.then(data => {
document.getElementById('message').innerText = data.message;
});
}
</script>
</body>
</html>
進入 <a href="/form">表單頁面</a>
連結到表單頁面,讓使用者能夠前往 /form 頁面,進行 GET 和 POST 操作。web/public/form.html
<html>
<head>
<meta charset="UTF-8">
<title>表單頁面</title>
</head>
<body>
<h1>表單操作</h1>
<h2>GET 請求</h2>
<!-- 搜尋表單 -->
<form action="/search" method="get">
<input type="text" name="keyword" placeholder="請輸入關鍵字">
<input type="submit" value="搜尋">
</form>
<h2>POST 表單</h2>
<!-- 登入表單 -->
<form action="/submit" method="post">
<input type="text" name="username" placeholder="帳號">
<input type="password" name="password" placeholder="密碼">
<input type="submit" value="送出">
</form>
</body>
</html>
模擬登入
功能,透過POST方式送出帳號和密碼,模擬登入表示尚未串接資料庫和其他驗證帳號密碼的方法。web/server.js
(1/3)
// 設定 form
app.get('/form', (req, res) => {
// 當存取 form 路徑時,請求 form.html 檔案
res.sendFile(path.join(__dirname, 'public', 'form.html'));
});
使用 Express.js 設定一個 GET 路由,當使用者存取 /form
路徑時,伺服器會傳送位於 public
資料夾中的 form.html
檔案。res.sendFile()
方法用於傳送指定路徑的檔案作為回應。
form.html
包含敏感資訊,直接暴露給所有使用者可能帶來風險。應確保該檔案不含機密資料,或實施適當的存取控制。/form
路徑。如果該表單僅適用於特定使用者,應該加入身份驗證或授權機制。web/server.js
(2/3)// 設定搜尋路由
app.get('/search', (req, res) => {
// 取得查詢字串參數
const keyword = req.query.keyword;
// 顯示查詢字串參數
res.send(`你搜尋的關鍵字是:${keyword}`);
});
這段程式碼設定一個 GET 路由 /search
。當使用者在查詢字串中提供 keyword
參數時,伺服器會取得該參數並在回應中顯示。例如,存取 /search?keyword=測試
,伺服器會回應「你搜尋的關鍵字是:測試」。
反射型跨站腳本攻擊(Reflected XSS):直接將使用者輸入的 keyword
顯示在回應中,且未經任何過濾或轉義,攻擊者可以藉此插入惡意腳本。例如,存取 /search?keyword=<script>alert('XSS')</script>
,可能導致使用者瀏覽器執行惡意腳本。
資訊洩漏:如果回應中包含敏感資訊,可能會被未經授權的第三方攔截或利用。
web/server.js
(3/3)// 使用 express.json() 來解析 JSON 格式的請求
app.use(express.json());
// 使用 express.urlencoded() 來解析 URL 編碼的請求
app.use(express.urlencoded({ extended: true }));
// 設定表單提交路由
app.post('/submit', (req, res) => {
// 取得 POST 請求的參數
const { username, password } = req.body;
// 顯示 POST 請求的參數
res.send(`你的帳號是 ${username},密碼是 ${password}`);
});
這段程式碼使用中介軟體 express.json()
和 express.urlencoded()
來解析請求主體中的 JSON 和 URL 編碼資料。然後,設定了一個 POST 路由 /submit
,從請求主體中解構取得 username
和 password
,並在回應中顯示這些資訊。
username
和 password
進行任何驗證,可能使系統易受 SQL 注入或其他注入式攻擊的影響。username
或 password
包含惡意腳本,且未經過濾,可能導致 XSS 攻擊。docker-compose up
,啟動服務。http://localhost:3000
,看到主頁內容。/form
,進行GET和POST請求的測試。在接受使用者輸入時,如果未對輸入內容進行適當的驗證和過濾,可能會導致 HTML注入、CSS注入、和 JavaScript注入 等安全漏洞。
例如:
git checkout db6597ae93f1d0e19879c69920021346c3850be7
切換到該 Commit。sudo apt install git
git clone https://github.com/fei3363/ithelp_web_security_2024.git
cd ithelp_web_security_2024/
git checkout db6597ae93f1d0e19879c69920021346c3850be7
切換到該 Commit
index.html
)<a>
標籤,可以連到 href
指定的網頁。form.html
)/search
。/submit
。目標
http://nodelab.feifei.tw/search?keyword=%E6%B8%AC%E8%A9%A6
http://nodelab.feifei.tw/search?keyword=%E6%B8%AC%E8%A9%A6
Protocol: http://
Domain: nodelab.feifei.tw
Path: /search
Query: ?keyword=%E6%B8%AC%E8%A9%A6
Protocol(協定): http://
Domain(網域): nodelab.feifei.tw
nodelab
是子域名,feifei.tw
是主域名。Path(路徑): /search
Query(查詢): ?keyword=%E6%B8%AC%E8%A9%A6
keyword
是參數名稱。%E6%B8%AC%E8%A9%A6
是經過 URL 編碼的參數值。特別說明查詢部分:
%E6%B8%AC%E8%A9%A6
是 URL 編碼後的中文字符「測試」。POST 是 HTTP 協議中常用的請求方法之一,主要用於向伺服器提交資料。與 GET 方法不同,POST 方法將資料放在請求主體(body)中,而不是 URL 中。
Content-Type: application/x-www-form-urlencoded
Payload (請求主體):
username=admin&password=password
&
分隔。server.js
)輸入: <h1>這是標題</h1>
結果: 所輸入的內容被解析成 HTML
解釋:
輸入: <style>body { background-color: red; }</style>
結果: 頁面背景變為紅色
解釋:
輸入: <script>alert('你被攻擊了!');</script>
結果: 彈出警告框
解釋:
// 使用 he
const he = require('he');
app.post('/submit', (req, res) => {
const { username, password } = req.body;
res.send(`你的帳號是 ${he.encode(username)},密碼是 ${he.encode(password)}`);
});
// 使用 sanitize-html
const sanitizeHtml = require('sanitize-html');
app.post('/submit', (req, res) => {
const { username, password } = req.body;
res.send(`你的帳號是 ${sanitizeHtml(username)},密碼是 ${sanitizeHtml(password)}`);
});
// 使用 DOMPurify (需要在 Node.js 環境中使用 jsdom)
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
app.post('/submit', (req, res) => {
const { username, password } = req.body;
res.send(`你的帳號是 ${DOMPurify.sanitize(username)},密碼是 ${DOMPurify.sanitize(password)}`);
});
he
npm install he
sanitize-html
npm install sanitize-html
DOMPurify
npm install dompurify jsdom
透過本次的教學,我們了解了使用者輸入與輸出的基本操作,以及可能存在的安全問題。在未來的學習中,我們將深入探討如何加強應用程式的安全性,防範各種潛在的攻擊。
git clone https://github.com/fei3363/ithelp_web_security_2024.git
git checkout db6597ae93f1d0e19879c69920021346c3850be7
<h1>這是一個測試</h1>
<script>alert('XSS 測試')</script>
完成上述任務後,嘗試實施一些基本的安全措施。例如: